import os
import time
import gnomevfs
import tempfile
import logging

from urllib import unquote
from RemoteResource import urlutil, NotFoundException
from RemoteResource.wdsession import WebDAVSession
from RemoteResource.user import User
from RemoteResource.wdproperties import WDPropertyNamespace
from RemoteResource.wdproperties import createPropertySetForResource
from WebDAV.davlib import HTTPBasicAuthenticator

_session = None
_session_path = None

def setup_logging():
    root = logging.getLogger()
    hdlr = logging.FileHandler('/tmp/plone-vfs-method.log')
    formatter = logging.Formatter(
        '%(asctime)s %(name)s:%(levelname)s %(message)s')
    hdlr.setFormatter(formatter)
    root.handlers = [hdlr]
    level = logging.INFO
    root.setLevel(level)

setup_logging()
logger = logging.getLogger('plone')

WHAT_TO_DEBUG = set(['io', 'file', 'dir'])  # change to what you need

class debug:
    """ Decorator which helps to control what aspects of a program to debug
    on per-function basis. Aspects are provided as list of arguments.
    It DOESN'T slowdown functions which aren't supposed to be debugged.
    """
    def __init__(self, aspects=None):
        self.aspects = set(aspects)

    def __call__(self, f):
        if self.aspects & WHAT_TO_DEBUG:
            def newf(*args, **kwds):
                logger.info('%s: %s %s', f.func_name, repr(args), repr(kwds))
                try:
                    f_result = f(*args, **kwds)
                except:
                    logger.exception('Exception in %s', f.func_name)
                    raise
                else:
                    if f_result is not None:
                        logger.debug('%s returned %s', f.func_name, repr(f_result))
                    return f_result
            newf.__doc__ = f.__doc__
            return newf
        else:
            return f

class TemporaryHandle(object):

    def __init__(self, *args, **kw):
        if not kw.has_key('uri'):
            raise ValueError, "Required keyword argument 'uri' missing"
        self.uri = kw['uri']
        del kw['uri']
        self.fhandle = tempfile.TemporaryFile(*args, **kw)

    def __getattribute__(self, name):
        if name in ('uri', 'fhandle'):
            return object.__getattribute__(self, name)
        return getattr(self.fhandle, name)

class Handle(object):

    def __init__(self, rsrc, uri):
        self.rsrc = rsrc
        self.uri = uri
        self._fhandle = None

    def _handle(self):
        if getattr(self, '_fhandle', None) is not None:
            return self._fhandle
        self._fhandle = self.rsrc.getDataStream()
        return self._fhandle

    fhandle = property(_handle)

    @debug(['trace'])
    def __getattribute__(self, name):
        if name in ('mode', '_fhandle', 'close', 'seek', 'tell',
                    'rsrc', 'uri', 'fhandle'):
            return object.__getattribute__(self, name)
        return getattr(self.fhandle, name)

    @debug(['io', 'file'])
    def close(self):
        handle = getattr(self, '_fhandle', None)
        if handle is not None:
            handle.read()
            handle.close()


@debug(['generic'])
def ensureSession(uri):
    global _session, _session_path
    if _session is not None:
        return _session
    authenticator = HTTPBasicAuthenticator(uri.user_name, uri.password)
    _session_path = urlutil.dirname(uri.path)
    url = 'http://%s:%s%s' % (uri.host_name, uri.host_port,
                              unquote(_session_path))
    _session = WebDAVSession(url, authenticator)
    return _session

@debug(['generic'])
def getResource(path):
    path = unquote(path)
    if not path.startswith(_session_path):
        raise gnomevfs.NotFoundError, path
    path = path[len(_session_path):]
    parts = filter(None, path.strip('/').split('/'))
    try:
        item = _session.getRoot()
        for part in parts:
            item = item.getChild(part)
    except NotFoundException:
        # print 'not found', path, _session_path
        raise gnomevfs.NotFoundError, path
    return item

@debug(['generic'])
def fileInfoFromResource(fileinfo, rsrc):
    fileinfo.name = rsrc.getId()

    # MODE, INO, DEV, NLINK, UID, GID, SIZE, ATIME, MTIME, CTIME
    # MTIME is a datetime
    # SIZE is a long
    stat = rsrc.getStatInfo()
    fileinfo.mtime = long(time.mktime(stat[8].utctimetuple()))
    fileinfo.ctime = fileinfo.mtime
    fileinfo.atime = long(time.mktime(time.gmtime()))
    fileinfo.size = stat[6]
    fileinfo.flags = gnomevfs.FILE_FLAGS_NONE

    fileinfo.gid = os.getgid()
    fileinfo.uid = os.getuid()

    if rsrc.isCollection():
        fileinfo.type = gnomevfs.FILE_TYPE_DIRECTORY
        fileinfo.mime_type = 'x-directory/normal'
        fileinfo.permissions = (gnomevfs.PERM_USER_ALL
                                | gnomevfs.PERM_OTHER_READ
                                | gnomevfs.PERM_OTHER_EXEC
                                | gnomevfs.PERM_GROUP_READ
                                | gnomevfs.PERM_GROUP_EXEC)
        fileinfo.access = (gnomevfs.PERM_ACCESS_READABLE
                           | gnomevfs.PERM_ACCESS_WRITABLE
                           | gnomevfs.PERM_ACCESS_EXECUTABLE)
    else:
        fileinfo.type = gnomevfs.FILE_TYPE_REGULAR
        # getMimeType() might return unicode *sigh*
        fileinfo.mime_type = str(rsrc.getMimeType())
        fileinfo.permissions = (gnomevfs.PERM_USER_READ
                                | gnomevfs.PERM_USER_WRITE
                                | gnomevfs.PERM_OTHER_READ
                                | gnomevfs.PERM_GROUP_READ)
        fileinfo.access = (gnomevfs.PERM_ACCESS_READABLE
                           | gnomevfs.PERM_ACCESS_WRITABLE)

@debug(['generic'])
def is_same_fs(a, b):
    return (a.scheme == b.scheme and
            a.host_name == b.host_name and
            a.host_port == b.host_port and
            a.user_name == b.user_name and
            a.password == b.password)

class DirectoryHandle:

    children = None

    def __init__(self, path):
        self.path = path

    @debug(['io', 'dir'])
    def iterChildren(self):
        return getResource(self.path).yieldChildren()

class plone_method:

    def __init__(self, module, handle):
        pass

    @debug(['dir'])
    def vfs_open_directory(self, uri, *args):
        # print 'open_directory', uri
        ensureSession(uri)
        return DirectoryHandle(uri.path)

    @debug(['dir'])
    def vfs_close_directory(self, handle, context):
        # print 'close_directory', handle, context
        pass

    @debug(['dir'])
    def vfs_read_directory(self, handle, fileinfo, context):
        # print 'read_directory'
        if handle.children is None:
            handle.children = handle.iterChildren()

        try:
            rsrc = handle.children.next()
        except StopIteration:
            raise gnomevfs.EOFError

        fileInfoFromResource(fileinfo, rsrc)

    @debug(['generic'])
    def vfs_is_local(self, *args):
        return False

    @debug(['io', 'generic', 'file', 'dir'])
    def vfs_get_file_info(self, uri, fileinfo, hmm, context):
        # print 'get_file_info', uri, fileinfo
        ensureSession(uri)
        rsrc = getResource(uri.path)
        fileInfoFromResource(fileinfo, rsrc)
        return fileinfo

    @debug(['io', 'dir'])
    def vfs_make_directory(self, uri, mode, context):
        # print 'make_directory'
        ensureSession(uri)
        parent = getResource(urlutil.dirname(uri.path))
        name = unquote(urlutil.basename(uri.path))
        if name in parent.listChildrenIds():
            raise gnomevfs.FileExistsError
        parent.createCollection(name)

    @debug(['io', 'dir'])
    def vfs_remove_directory(self, uri, context):
        # print 'remove_directory'
        ensureSession(uri)
        parent = getResource(urlutil.dirname(uri.path))
        name = unquote(urlutil.basename(uri.path))
        # XXX Check if really a directory, raise NotADirectoryError?
        # XXX Check if empty directory
        parent.deleteChild(name)

    @debug(['io', 'file'])
    def vfs_unlink(self, uri, context):
        # print 'unlink', uri
        ensureSession(uri)
        parent = getResource(urlutil.dirname(uri.path))
        name = unquote(urlutil.basename(uri.path))
        parent.deleteChild(name)

    @debug(['io', 'generic', 'file', 'dir'])
    def vfs_set_file_info(self, uri, fileinfo, mask, context):
        # print 'set_file_info'
        # SET_FILE_INFO_NONE: don't set any file info fields
        # SET_FILE_INFO_NAME: change the name
        # SET_FILE_INFO_PERMISSIONS: change the permissions
        # SET_FILE_INFO_OWNER: change the file's owner
        # SET_FILE_INFO_TIME: change the file's time stamp
        if mask & gnomevfs.SET_FILE_INFO_NAME:
            # Set the name, a.k.a rename the file.
            ensureSession(uri)
            parent = getResource(urlutil.dirname(uri.path))
            name = unquote(urlutil.basename(uri.path))
            parent.renameChild(name, unquote(fileinfo.name))

    @debug(['io', 'file', 'dir'])
    def vfs_move(self, old_uri, new_uri, force, context=None):
        # print 'move', old_uri, new_uri, force
        # Check source and target URIs to make sure they are in the
        # same filesystem.
        # XXX We don't support renaming the file being moved yet.
        if not is_same_fs(old_uri, new_uri):
            raise gnomevfs.NotSameFileSystemError
        ensureSession(old_uri)
        ensureSession(new_uri)
        source = getResource(old_uri.path)
        target = getResource(urlutil.dirname(new_uri.path))
        name = unquote(urlutil.basename(old_uri.path))
        if name in target.listChildrenIds():
            if not force:
                raise gnomevfs.FileExistsError
            else:
                target.deleteChild(name)
        target.pasteCutChild(source)

    @debug(['generic'])
    def vfs_check_same_fs(self, source_uri, target_uri, *args):
        # print 'check_same_fs'
        return is_same_fs(source_uri, target_uri)

    @debug(['io', 'file', 'dir'])
    def vfs_create(self, uri, mode, exclusive, perm, context):
        # print 'create', uri, mode, exclusive, perm, context
        ensureSession(uri)
        parent = getResource(urlutil.dirname(uri.path))
        name = unquote(urlutil.basename(uri.path))
        if not mode & gnomevfs.OPEN_WRITE:
            raise gnomevfs.NotSupportedError
        if exclusive and name in parent.listChildrenIds():
            raise gnomevfs.FileExistsError
        return TemporaryHandle(uri=uri)

    @debug(['io', 'file'])
    def vfs_open(self, uri, mode, context):
        # print 'open', uri, mode, context
        if mode & gnomevfs.OPEN_RANDOM:
            raise gnomevfs.NotSupportedError, mode
        if mode & gnomevfs.OPEN_WRITE:
            raise gnomevfs.NotSupportedError, mode
        if mode & gnomevfs.OPEN_READ:
            ensureSession(uri)
            rsrc = getResource(uri.path)
            return Handle(rsrc, uri)
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_close(self, fhandle, context):
        # print 'close', fhandle, context
        if fhandle is None:
            raise gnomevfs.NotSupportedError
        if 'w' in getattr(fhandle, 'mode', ''):
            # Upload file.
            uri = fhandle.uri
            ensureSession(uri)
            parent = getResource(urlutil.dirname(uri.path))
            name = unquote(urlutil.basename(uri.path))
            try:
                if not name in parent.listChildrenIds():
                    # Not yet existing
                    parent.createChild(name, fhandle)
                else:
                    # Existing resource
                    rsrc = getResource(uri.path)
                    rsrc.save(fhandle)
            finally:
                fhandle.close()
        else:
            # Just close the handle.
            fhandle.close()

    @debug(['io', 'file'])
    def vfs_read(self, handle, wbuffer, bytes, context):
        # print 'read' #, handle, type(wbuffer), bytes, context
        read = handle.read(bytes)
        bytes_written = len(read)
        wbuffer[:bytes_written] = read
        return bytes_written

    @debug(['io', 'file'])
    def vfs_write(self, handle, rbuffer, bytes, context):
        # print 'write' #, handle, type(rbuffer), bytes, context
        if handle is not None:
            handle.write(rbuffer[:bytes])
            return bytes
        raise gnomevfs.NotSupportedError

    @debug(['generic', 'file', 'dir'])
    def vfs_get_file_info_from_handle(self, *args):
        # print 'get_file_info_from_handle', args
        raise gnomevfs.NotSupportedError

    @debug(['dir'])
    def vfs_find_directory(self, *args):
        # print 'find_directory'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_seek(self, *args):
        # print 'seek'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_tell(self, *args):
        # print 'tell'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_truncate_handle(self, *args):
        # print 'truncate_handle'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_truncate(self, *args):
        # print 'truncate'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'file'])
    def vfs_create_symbolic_link(self, *args):
        # print 'create_symbolic_link'
        raise gnomevfs.NotSupportedError

    @debug(['io', 'generic', 'file', 'dir'])
    def vfs_file_control(self, handle, operation, operation_data, context):
        # print handle, operation, operation_data, context
        ensureSession(handle.uri)
        if operation in ('plone:propertyset',):
            pns = WDPropertyNamespace(_session, operation_data)
            ps = createPropertySetForResource(pns, handle.rsrc)
            return handle.rsrc, ps
        elif operation in ('plone:operations',):
            return handle.rsrc, _session.getOperations([handle.rsrc])
        return None
